package com.bigshen.mvc.spring.servlet;

import com.bigshen.mvc.spring.annonation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @Author eddy.shen
 * @Date 2020/4/5 15:52
 * @Desc dispatcher类模拟Spring处理流程
 **/
public class ShenDispatcherServlet extends HttpServlet {

    // 配置文件properties，内部配置了类的扫描路径
    private Properties properties = new Properties();
    // 扫描路径下的所有class
    private List<String> classList = new ArrayList<>();
    // ioc容器管理bean
    private Map<String, Object> iocMap = new HashMap<>();
    // url对应映射关系
    private Map<String, HandlerMapping> urlMap = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }

    // 分发匹配对应url
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {

        String rootPath = req.getContextPath();
        // 请求url的路径，去掉项目前缀
        String path = req.getRequestURI().replace(rootPath, "");
        if (!urlMap.containsKey(path)) {
            throw new RuntimeException("404 not found!");
        }

        HandlerMapping handler = urlMap.get(path);

        Method method = handler.getMethod();
        Object instance = handler.getInstance();

        Class[] paramTypes = handler.getParameterTypes();
        // 参数值数组
        Object[] paramValues = new Object[paramTypes.length];
        // 本次请求传入参数
        Map<String, String[]> requestParamMap = req.getParameterMap();

        for (Map.Entry<String, Integer> entry : handler.getParameterIndexMap().entrySet()) {

            String paramName = entry.getKey();
            Integer index = entry.getValue();

            if (paramName.equals(HttpServletRequest.class.getName())) {
                // 为方法中的HttpServletRequest类型赋值
                paramValues[index] = req;
                continue;
            }

            if (paramName.equals(HttpServletResponse.class.getName())) {
                // 为方法中的HttpServletResponse类型赋值
                paramValues[index] = resp;
                continue;
            }

            if (requestParamMap.containsKey(paramName)) {
                // 为方法中使用spring注解的参数赋值
                String[] value = requestParamMap.get(paramName);
                // 根据参数类型class将值转换为对应类型
                paramValues[index] = convert(paramTypes[index], value);
            }

        }

        try {
            //反射执行url所请求的对应method
           Object result = method.invoke(instance, paramValues);
            if(result == null || result instanceof Void){ return; }
            resp.getWriter().write(result.toString());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private Object convert(Class paramType, String[] value) {

        String paramValue = Arrays.toString(value).replaceAll("\\[|\\]","")
                .replaceAll("\\s",",");

        if (paramType.equals(Integer.class)) {
            return Integer.valueOf(paramValue);
        }
        if (paramType.equals(Double.class)) {
            return Double.valueOf(paramValue);
        }
        // .... 简单方式转换，待补充
        return paramValue;
    }


    @Override
    public void init(ServletConfig config) throws ServletException {

        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 2.根据配置中指定的扫描路径进行扫描
        doScanner(properties.getProperty("scanPackage"));

        // 3.ioc、匹配的类放入ioc容器
        doIOC();

        //TODO doAOP() 在依赖注入前通过字节码重组生成新的代理类，进行依赖注入

        // 4.di、将ioc容器中需要DI依赖注入的进行注入
        doDI();

        // 5.mvc、扫描controller类的method，将url与method绑定映射
        initHandlerMapping();

        System.out.println("Spring framework is init.");

    }


    // 加载配置文件，转为properties
    private void doLoadConfig(String scanPackage) {

        // 读取web.xml中配置的properties文件
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(scanPackage);
        try {
            // 转为properties
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 根据配置中指定的扫描路径进行扫描
    private void doScanner(String scanPackage) {

        // 将scanPackage扫描路径中的 . 转为 /
        String packagePath = scanPackage.replaceAll("\\.", "/");
        URL url = this.getClass().getClassLoader().getResource("/" + packagePath);
        File file = new File(url.getFile());

        if (file.isDirectory()) {
            // 如果是路径，遍历子路径，递归查询
            File[] childFile = file.listFiles();
            for (File child : childFile) {

                if (child.isFile() && child.getName().endsWith(".class")) {
                    // 是文件且是class文件，先统计出来
                    String className = child.getName();
                    String classPath = scanPackage + "." + className.replace(".class", "");
                    classList.add(classPath);
                } else if (child.isDirectory()) {
                    // 文件夹、递归
                    doScanner(scanPackage + "." + child.getName());
                }

            }
        }



    }

    // 遍历扫描到的类，如果符合Spring注解则交由IOC容器管理
    private void doIOC() {

        if (classList.isEmpty()) {return;}

        for (String clazzName : classList) {
            try {
                Class clazz = Class.forName(clazzName);
                if (clazz.isAnnotationPresent(ShenController.class)) {
                    // 如果类上有Controller类型注解
                    // 以类全名为key，将类对象创建出来放入 ioc容器
                    iocMap.put(clazz.getName(), clazz.newInstance());
                } else if (clazz.isAnnotationPresent(ShenService.class)) {
                    // 如果类上有Service类型注解
                    ShenService service = (ShenService) clazz.getAnnotation(ShenService.class);
                    String value = service.value();
                    Boolean isEmpty = null == value || "".equals(value.trim()) ? true : false;
                    Boolean isInterface = clazz.getInterfaces().length > 0 ? true : false;
                    // ioc中key有几种情况
                    if (!isEmpty) {
                        // 1. 注解中value有值
                        Object exist = iocMap.get(value);
                        if (null != exist) {
                            throw new RuntimeException("IOC容器中存在重复的声明Bean【】" + value);
                        }
                        iocMap.put(value, clazz.newInstance());
                    } else if (!isInterface) {
                        // 2. 注解中value无值，且类没有实现接口
                        iocMap.put(clazz.getName(), clazz.newInstance());
                    } else {
                        // 3. 注解中value无值，类实现了接口
                        Class[] interfaces = clazz.getInterfaces();
                        Object instance = clazz.newInstance();
                        for (Class element : interfaces) {
                            // 为每个接口类型绑定同样的对象实例
                            Object exist = iocMap.get(element.getName());
                            if (null != exist) {
                                throw new RuntimeException("IOC容器中存在重复的声明Bean【】" + value);
                            }
                            iocMap.put(element.getName(), instance);
                        }
                    }

                }

            } catch (ClassNotFoundException e) {
                    e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }

    }

    // 遍历IOC容器中的对象的属性，如果使用了注入的注解，对属性进行依赖注入
    private void doDI() {

        if (iocMap.isEmpty()) {return;}

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

            String name = entry.getKey();
            Object instance = entry.getValue();
            Class instanceClazz = instance.getClass();
            Field[] fields = instanceClazz.getDeclaredFields();

            for (Field field : fields) {

                if (field.isAnnotationPresent(ShenAutowired.class)) {
                    ShenAutowired shenAutowired = field.getAnnotation(ShenAutowired.class);
                    String beanName = shenAutowired.value().trim();
                    if (null == beanName || "".equals(beanName)) {
                        Class fieldClazz = field.getType();
                        beanName = fieldClazz.getName();
                    }
                    // ioc容器中的属性上有spring注入注解
                    field.setAccessible(true);
                    try {
                        field.set(instance, iocMap.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

            }

        }

    }

    // 将controller中使用注解的method提取出，将URL与ioc容器中对象绑定，在接收到request请求后进行url匹配
    private void initHandlerMapping() {

        if (iocMap.isEmpty()) {return;}

        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {

            String name = entry.getKey();
            Object instance = entry.getValue();
            Class instanceClazz = instance.getClass();

            String rootPath = "";
            // 提取controller配置的url根路径
            if (instanceClazz.isAnnotationPresent(ShenController.class) && instanceClazz.isAnnotationPresent(ShenRequestMapping.class)) {
                ShenRequestMapping controllerMapping = (ShenRequestMapping) instanceClazz.getAnnotation(ShenRequestMapping.class);
                rootPath = controllerMapping.value();
            }

            Method[] methods = instanceClazz.getDeclaredMethods();
            for (Method method :methods) {
                // 为每个method提取独立的url，并记录映射关系
                if (method.isAnnotationPresent(ShenRequestMapping.class)) {
                    ShenRequestMapping methodMapping = method.getAnnotation(ShenRequestMapping.class);
                    String methodUrl = rootPath + methodMapping.value();
                    urlMap.put(methodUrl, new HandlerMapping(methodUrl, instance, method));
                }

            }



        }

    }

    class HandlerMapping {

        private String methodUrl;
        private Object instance;
        private Method method;
        // 方法类型
        private Class[] parameterTypes;
        // 参数值、参数位置对应关系
        private Map<String, Integer> parameterIndexMap = new HashMap<>();

        public HandlerMapping(String methodUrl, Object instance, Method method) {
            this.methodUrl = methodUrl;
            this.instance = instance;
            this.method = method;
            // 在映射关系中，根据method提取方法入参及顺序
            initMethodParameters();
        }

        // 提取method对应的参数映射关系
        private void initMethodParameters() {

            // 方法参数类型
            parameterTypes = method.getParameterTypes();

            // 根据参数注解找对应入参
            // 方法参数的注解类型, 一个方法有多个参数，每个参数可以有多个注解，所有是个二维数组
            Annotation[][] paramAnnotations = method.getParameterAnnotations();
            for(int i = 0; i < paramAnnotations.length; i ++) {
                Annotation[] paramAnnotationArray = paramAnnotations[i];
                for (Annotation annotation : paramAnnotationArray) {
                    if (annotation instanceof ShenRequestParam) {
                        ShenRequestParam requestParam = (ShenRequestParam) annotation;
                        String paramName = requestParam.value();
                        if (null != paramName && !"".equals(paramName.trim())) {
                            parameterIndexMap.put(paramName, i);
                        }
                    }
                }
            }

            // 固定的HttpServletRequest、HttpServletResponse
            for(int i = 0 ; i < parameterTypes.length; i++) {
                Class paramType = parameterTypes[i];
                if (paramType.equals(HttpServletRequest.class)) {
                    parameterIndexMap.put(HttpServletRequest.class.getName(), i);
                    continue;
                }
                if (paramType.equals(HttpServletResponse.class)) {
                    parameterIndexMap.put(HttpServletResponse.class.getName(), i);
                    continue;
                }
            }

        }

        public String getMethodUrl() {
            return methodUrl;
        }

        public Object getInstance() {
            return instance;
        }

        public Method getMethod() {
            return method;
        }

        public Class[] getParameterTypes() {
            return parameterTypes;
        }

        public Map<String, Integer> getParameterIndexMap() {
            return parameterIndexMap;
        }
    }

}

